using System;
using System.Collections.Generic;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Drawing;
using System.Text;
using System.Xml;
using System.Windows.Forms;
using System.Security.Cryptography;
using ICSharpCode.SharpZipLib.Zip;
using System.Reflection;
using LDBUtilities;

namespace SecurePasswords
{
    public partial class SecurePasswords : Form
    {
        //application full path
        private static string fullPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);
        private EditPasswordForm editForm = new EditPasswordForm();
        private MasterPasswordForm masterPasswordForm = new MasterPasswordForm();
        private static string PASSWORDS_FILE = fullPath+"\\SecurePasswords.zip";
        private string masterPassword = "";
        
        public SecurePasswords()
        {
            InitializeComponent();
            ShowMasterPasswordForm(null);
        }

        /// Shows the master password form.
        /// When the form is closed, tries to load the password file with the password given if file exist.
        private void ShowMasterPasswordForm(string overrideMessage) {
            //If password file exist, open it, otherwise generate master password
            if (File.Exists(PASSWORDS_FILE))
            {
                masterPasswordForm.HidePassword = true;
                masterPasswordForm.Message = overrideMessage != null?overrideMessage:"Enter master password:";
                masterPasswordForm.ShowDialog();
                masterPassword = masterPasswordForm.Password;
                try
                {
                    LoadPasswords();
                }
                catch (ZipException ex)
                {   
                    //password incorrct, recursively display form until password is right
                    ShowMasterPasswordForm("Error: "+ex.Message);
                }
            }
            else
            {
                masterPasswordForm.HidePassword = false;
                masterPasswordForm.Message = overrideMessage != null?overrideMessage:"Create a new master password:";
                masterPasswordForm.ShowDialog();
                masterPassword = masterPasswordForm.Password;
            }
        }


        /// <summary>
        /// Attempts to load the encrypted password file.
        /// MasterPassword must be set before this methos is called.
        /// </summary>
        private void LoadPasswords()
        {
            ZipFile f = new ZipFile(PASSWORDS_FILE);
            try
            {
                f.Password = masterPassword;
                XmlDocument xml = new XmlDocument();
                xml.Load(f.GetInputStream(0));
                XmlNodeList passwords = xml.DocumentElement.SelectNodes("password");
                if (passwords.Count > 0)
                {
                    lstEntries.Items.Clear();
                    foreach (XmlNode node in passwords)
                    {
                        string site = node.Attributes["site"].Value;
                        string username = node.Attributes["username"].Value;
                        string password = node.Attributes["password"].Value;
                        //try to get note, set to empty text if not exists
                        string note = node.SelectSingleNode("note") != null ? node.SelectSingleNode("note").InnerText : "";
                        lstEntries.Items.Add(new ListViewItem(new string[] { site, username, password, note }));
                    }
                }
                f.Close();
            }
            finally
            {
                //close file before throwing exception.
                //This is so the file does not stay open if an ivalid password causes an exception.
                if (f != null) f.Close();
            }
        }

        /// <summary>
        /// Writes the passwords to an xml encrypted with zip encryption using the selected password.
        /// </summary>
        private void SavePasswords()
        {
            //take a backup of existing passwords
            try
            {
                File.Delete(PASSWORDS_FILE.Replace(".zip", ".bak"));
                File.Move(PASSWORDS_FILE, PASSWORDS_FILE.Replace(".zip", ".bak"));
            }
            catch (FileNotFoundException) { }   //If no backup file found this is probably the first time we started
            
            //create xml file for passwords
            XmlDocument xml = new XmlDocument();
            xml.PrependChild(xml.CreateXmlDeclaration("1.0", "UTF-8", null));
            xml.AppendChild(xml.CreateElement("securepasswords"));
            foreach (ListViewItem item in lstEntries.Items)
            {
                XmlElement pw = xml.CreateElement("password");
                pw.Attributes.Append(xml.CreateAttribute("site"));
                pw.Attributes.Append(xml.CreateAttribute("username"));
                pw.Attributes.Append(xml.CreateAttribute("password"));
                pw.Attributes["site"].Value = item.SubItems[0].Text;
                pw.Attributes["username"].Value = item.SubItems[1].Text;
                pw.Attributes["password"].Value = item.SubItems[2].Text;
                if (item.SubItems[3].Text != null && item.SubItems[3].Text.Length > 0)
                {
                    XmlElement note = xml.CreateElement("note");
                    note.AppendChild(xml.CreateTextNode(item.SubItems[3].Text));
                    pw.AppendChild(note);
                }
                xml.DocumentElement.AppendChild(pw);
            }
            //save in encrypted zip file
            ZipOutputStream zout = new ZipOutputStream(System.IO.File.OpenWrite(PASSWORDS_FILE));
            ZipEntry ze = new ZipEntry("SecurePasswords.xml");
            zout.Password = masterPassword;
            zout.PutNextEntry(ze);
            zout.SetLevel(0);
            xml.Save(zout);
            zout.Finish();
            zout.Close();
        }

        /// <summary>
        /// Called when the user clicks the new button.
        /// Opens the EditPasswords form with empty fields.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnNew_Click(object sender, EventArgs e)
        {
            //clear text in form fields
            editForm.txtSite.Text = string.Empty;
            editForm.txtUsername.Text = string.Empty;
            editForm.txtPassword.Text = string.Empty;
            editForm.txtNote.Text = string.Empty;

            //add a new entry to list
            if (DialogResult.OK == editForm.ShowDialog())
            {
                lstEntries.Items.Add(new ListViewItem(new string[] { editForm.txtSite.Text, editForm.txtUsername.Text, editForm.txtPassword.Text, editForm.txtNote.Text }));
                SavePasswords();
            }
        }

        /// <summary>
        /// Called when the user clicks the edit button.
        /// Opens the EditPasswords form with the data from the selected entry.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnEdit_Click(object sender, EventArgs e)
        {
            //modify entry in list if one (and only one) is selected
            if (lstEntries.SelectedIndices.Count == 1)
            {
                //put text in form fields
                editForm.txtSite.Text = lstEntries.Items[lstEntries.SelectedIndices[0]].Text;
                editForm.txtUsername.Text = lstEntries.Items[lstEntries.SelectedIndices[0]].SubItems[1].Text;
                editForm.txtPassword.Text = lstEntries.Items[lstEntries.SelectedIndices[0]].SubItems[2].Text;
                editForm.txtNote.Text = lstEntries.Items[lstEntries.SelectedIndices[0]].SubItems[3].Text;

                if (DialogResult.OK == editForm.ShowDialog())
                {
                    lstEntries.Items.RemoveAt(lstEntries.SelectedIndices[0]);
                    lstEntries.Items.Add(new ListViewItem(new string[] { editForm.txtSite.Text, editForm.txtUsername.Text, editForm.txtPassword.Text, editForm.txtNote.Text }));
                    SavePasswords();
                }
            }

        }

        /// <summary>
        /// Called when the user tries to delete an entry.
        /// Shows an "are you sure" dialog.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDelete_Click(object sender, EventArgs e)
        {
            //delete if ONE selected and yes to messagebox
            if (lstEntries.SelectedIndices.Count == 1 && DialogResult.Yes == MessageBox.Show("Delete entry?", "Confirm delete", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2))
            {
                lstEntries.Items.RemoveAt(lstEntries.SelectedIndices[0]);
                SavePasswords();
            }
        }

        /// <summary>
        /// When a user activates (double taps) an entry this is the same clicking edit,
        /// so this method just calls the edit event handler.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lstEntries_ItemActivate(object sender, EventArgs e)
        {
            btnEdit_Click(sender, e);
        }

        /// <summary>
        /// Closes the form when the user chooses the exit menu.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void miExit_Click(object sender, EventArgs e)
        {
            Close();
        }


        /// <summary>
        /// Called whenever an input textfield needs to display input panel.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtBox_GotFocus(object sender, EventArgs e)
        {
            inputPanel1.Enabled = true;
        }

        /// <summary>
        /// Called whenever an input textfield no longer needs to display input panel.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtBox_LostFocus(object sender, EventArgs e)
        {
            inputPanel1.Enabled = false;
        }

        private void miAbout_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Copyright (C) 2006 Lars Daniel Benes.", "Secure Passwords V1.0");
        }

        /// <summary>
        /// Called when the user taps a column header. Performs sorting on that column.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lstEntries_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Create an instance of the ColHeader class.
            ColumnHeader clickedCol = this.lstEntries.Columns[e.Column];

            // Get the number of items in the list.
            int numItems = this.lstEntries.Items.Count;

            // Turn off display while data is repoplulated.
            this.lstEntries.BeginUpdate();

            // Populate an ArrayList with a SortWrapper of each list item.
            List<ListViewSortWrapper> SortArray = new List<ListViewSortWrapper>();
            for (int i = 0; i < numItems; i++)
            {
                SortArray.Add(new ListViewSortWrapper(this.lstEntries.Items[i], e.Column));
            }

            // Sort the elements in the ArrayList using a new instance of the SortComparer
            // class. The parameters are the starting index, the length of the range to sort,
            // and the IComparer implementation to use for comparing elements. Note that
            // the IComparer implementation (SortComparer) requires the sort
            // direction for its constructor; true if ascending, othwise false.
            SortArray.Sort(0, SortArray.Count, new ListViewSortWrapper.SortComparer(true));

            // Clear the list, and repopulate with the sorted items.
            this.lstEntries.Items.Clear();
            for (int i = 0; i < numItems; i++)
                this.lstEntries.Items.Add(SortArray[i].SortItem);

            // Turn display back on.
            this.lstEntries.EndUpdate();
        }


    }
}